package com.foundationmade.newbiz.business.foundation.service;

import java.util.List;

import javax.annotation.Resource;

import org.apache.commons.lang.StringUtils;
import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.criterion.CriteriaSpecification;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.foundationmade.newbiz.business.foundation.domain.Foundation;

import devcken.hibernate.query.OrderBy;
import devcken.utility.Pair;

/**
 * 매장에 대한 데이터베이스 액세스를 제공한다.
 * @author Devcken
 */
@Service("foundationService")
@Transactional
public class FoundationService
{
	/**
	 * NewBiz {@link org.hibernate.SessionFactory}를 주입받는다.
	 */
	@Resource(name="newbizSessionFactory")
	private SessionFactory _sessionFactory;
	
	/**
	 * 주어진 검색 조건으로 매장 목록을 조회한다.
	 * @param searchCondition 매장 목록을 조회하기 위한 검색 조건을 설정한다.
	 * @param searchLeaseDepositLow
	 * @param searchLeaseDepositHigh
	 * @param searchFacilitiesInvestmentLow
	 * @param searchFacilitiesInvestmentHigh
	 * @param searchPremiumLow
	 * @param searchPremiumHigh
	 * @param searchUnionFeeLow
	 * @param searchUnionFeeHigh
	 * @param searchSupportFundLow
	 * @param searchSupportFundHigh
	 * @param searchTotalFeeLow
	 * @param searchTotalFeeHigh
	 * @param searchMonthlyRentalFeeLow
	 * @param searchMonthlyRentalFeeHigh
	 * @param searchCommisionLow
	 * @param searchCommisionHigh
	 * @param searchServiceChargeHigh
	 * @param searchServiceChargeLow
	 * @param searchSalesPerDayLow
	 * @param searchSalesPerDayHigh
	 * @param searchSalesPerMonthHigh
	 * @param searchSalesPerMonthLow
	 * @param page 표시할 페이지를 설정한다.
	 * @param pageSize 한 페이지 당 표시할 레코드 수를 설정한다.
	 * @param sortConditions 정렬 조건을 설정한다.
	 * @param foundations 조회된 매장 목록을 참조한다.
	 * @return 검색 조건으로 조회된 전체 매장의 개수가 반환된다.
	 */
	@SuppressWarnings("unchecked")
	public Integer listFoundations(Foundation searchCondition,
			Integer searchLeaseDepositLow, Integer searchLeaseDepositHigh,
			Integer searchFacilitiesInvestmentLow, Integer searchFacilitiesInvestmentHigh,
			Integer searchPremiumLow, Integer searchPremiumHigh, 
			Integer searchUnionFeeLow, Integer searchUnionFeeHigh,
			Integer searchSupportFundLow, Integer searchSupportFundHigh,
			Integer searchTotalFeeLow, Integer searchTotalFeeHigh,
			Integer searchMonthlyRentalFeeLow, Integer searchMonthlyRentalFeeHigh,
			Integer searchCommisionLow, Integer searchCommisionHigh,
			Float searchServiceChargeHigh, Float searchServiceChargeLow,
			Integer searchSalesPerDayLow, Integer searchSalesPerDayHigh,
			Integer searchSalesPerMonthHigh, Integer searchSalesPerMonthLow,
			int page,
			int pageSize,
			List<Pair<String, OrderBy>> sortConditions,
			List<Foundation> foundations)
	{
		Session session = this._sessionFactory.openSession();
		
		try
		{
			Criteria criteria = session.createCriteria(Foundation.class);
			
			// 매장 명칭이 검색 조건으로 설정된 경우, 매장 명칭 검색 조건을 부여한다. 
			if (StringUtils.isNotEmpty(searchCondition.getFoundationName()))
			{
				criteria = criteria.add(Restrictions.like("foundationName", searchCondition.getFoundationName()));
			}
			
			// 매장 주소가 검색 조건으로 설정된 경우, 주소 검색 조건을 부여한다.
			if (StringUtils.isNotEmpty(searchCondition.getAddress()))
			{
				criteria = criteria.add(Restrictions.like("address", searchCondition.getAddress()));
			}
			
			// 임대 보증금이 검색 조건으로 설정된 경우, 임대 보증금 검색 조건을 부여한다.
			// 임대 보증금은 범위로 검색하며 상한값이 0보다 커야하며, 상한값이 하한값보다 커야 한다. 
			if (searchLeaseDepositHigh != null && searchLeaseDepositHigh > 0 &&
					searchLeaseDepositHigh > searchLeaseDepositLow)
			{
				criteria = criteria.add(Restrictions.between("leaseDeposit", searchLeaseDepositLow, searchLeaseDepositHigh));
			}
			
			// 시설 투자금이 검색 조건으로 설정된 경우, 시설 투자금 검색 조건을 부여한다.
			// 시설 투자금은 범위로 검색하며 상한값이 0보다 커야하며, 상한값이 하한값보다 커야 한다.
			if (searchFacilitiesInvestmentHigh != null && searchFacilitiesInvestmentHigh > 0 &&
					searchFacilitiesInvestmentHigh > searchFacilitiesInvestmentLow)
			{
				criteria = criteria.add(Restrictions.between("facilitiesInvestment", searchFacilitiesInvestmentLow, searchFacilitiesInvestmentHigh));
			}
			
			// 권리금이 검색 조건으로 설정된 경우, 권리금 검색 조건을 부여한다.
			// 권리금은 범위로 검색하며 상한값이 0보다 커야하며, 상한값이 하한값보다 커야 한다.
			if (searchPremiumLow != null && searchPremiumHigh > 0 &&
					searchPremiumHigh > searchPremiumLow)
			{
				criteria = criteria.add(Restrictions.between("premium", searchPremiumLow, searchPremiumHigh));
			}
			
			// 가맹비가 검색 조건으로 설정된 경우, 가맹비 검색 조건을 부여한다.
			// 가맹비는 범위로 검색하며 상한값이 0보다 커야 하며, 상한값이 하한값보다 커야 한다.
			if (searchUnionFeeHigh != null && searchUnionFeeHigh > 0 &&
					searchUnionFeeHigh > searchUnionFeeLow)
			{
				criteria = criteria.add(Restrictions.between("unionFee", searchUnionFeeLow, searchUnionFeeHigh));
			}
			
			// 운영지원금이 검색 조건으로 설정된 경우, 운영지원금 검색 조건을 부여한다.
			// 운영지원금은 범위로 검색하며 상한값이 0보다 커야 하며, 상한값이 하한값보다 커야 한다.
			if (searchSupportFundHigh != null && searchSupportFundLow > 0 &&
					searchSupportFundHigh > searchSupportFundLow)
			{
				criteria = criteria.add(Restrictions.between("supportFund", searchSupportFundLow, searchSupportFundHigh));
			}
			
			// 합계가 검색 조건으로 설정된 경우, 합계 검색 조건을 부여한다.
			// 합계는 범위로 검색하며 상한값이 0보다 커야 하며, 상한값이 하한값보다 커야 한다.
			if (searchTotalFeeHigh != null && searchTotalFeeHigh > 0 &&
					searchTotalFeeHigh > searchTotalFeeLow)
			{
				criteria = criteria.add(Restrictions.between("totalFee", searchTotalFeeLow, searchTotalFeeHigh));
			}
			
			// 월세/관리비가 검색 조건으로 설정된 경우, 월세/관리비 검색 조건을 부여한다.
			// 월세/관리비는 범위로 검색하며 상한값이 0보다 커야 하며, 상한값이 하한값보다 커야 한다.
			if (searchMonthlyRentalFeeHigh != null && searchMonthlyRentalFeeHigh > 0 &&
					searchMonthlyRentalFeeHigh > searchMonthlyRentalFeeLow)
			{
				criteria = criteria.add(Restrictions.between("monthlyRentalFee", searchMonthlyRentalFeeLow, searchMonthlyRentalFeeHigh));
			}
			
			// 수수료/관리비가 검색 조건으로 설정된 경우, 수수료/관리비 검색 조건을 부여한다.
			// 수수료/관리비는 범위로 검색하며 상한값이 0보다 커야 하며, 상한값이 하한값보다 커야 한다. 
			if (searchCommisionHigh != null && searchCommisionHigh > 0 &&
					searchCommisionHigh > searchCommisionLow)
			{
				criteria = criteria.add(Restrictions.between("commision", searchCommisionLow, searchCommisionHigh));
			}
			
			// 용역비가 검색 조건으로 설정된 경우, 용역비 검색 조건을 부여한다.
			// 용역비는 범위로 검색하며 상한값이 0보다 커야 하며, 상한값이 하한값보다 커야 한다.
			if (searchServiceChargeHigh != null && searchServiceChargeHigh > 0.0 &&
					searchServiceChargeHigh > searchServiceChargeLow)
			{
				criteria = criteria.add(Restrictions.between("serviceCharge", searchServiceChargeLow, searchServiceChargeHigh));
			}
			
			// 일매출이 검색 조건으로 설정된 경우, 일매출 검색 조건을 부여한다.
			// 일매출는 범위로 검색하며 상한값이 0보다 커야 하며, 상한값이 하한값보다 커야 한다.
			if (searchSalesPerDayHigh != null && searchSalesPerDayHigh > 0 &&
					searchSalesPerDayHigh > searchSalesPerDayLow)
			{
				criteria = criteria.add(Restrictions.between("salesPerDayHigh", searchSalesPerDayLow, searchSalesPerDayHigh));
			}
			
			// 월매출이 검색 조건으로 설정된 경우, 월매출 검색 조건을 부여한다.
			// 월매출는 범위로 검색하며 상한값이 0보다 커야 하며, 상한값이 하한값보다 커야 한다.
			if (searchSalesPerMonthHigh != null && searchSalesPerMonthHigh > 0 &&
					searchSalesPerMonthHigh > searchSalesPerMonthLow)
			{
				criteria = criteria.add(Restrictions.between("salesPerMonth", searchSalesPerMonthLow, searchSalesPerMonthHigh));
			}
			
			// 운영 방식이 검색 조건으로 설정된 경우, 운영 방식 검색 조건을 부여한다. 
			if (StringUtils.isNotEmpty(searchCondition.getOperatingMethod()))
			{
				criteria = criteria.add(Restrictions.like("operatingMethod", searchCondition.getOperatingMethod()));
			}
			
			if (sortConditions.size() == 0)
			{
				criteria.addOrder(Order.desc("generatedTimestamp"));
			}
			
			for (int index = 0; index < sortConditions.size(); index++)
			{
				Pair<String, OrderBy> sortCondition = sortConditions.get(index);
				
				switch (sortCondition.getRight())
				{
					case Ascending:
						
						criteria.addOrder(Order.asc(sortCondition.getLeft()));
						
						break;
						
					case Descending:
						
						criteria.addOrder(Order.desc(sortCondition.getLeft()));
						
						break;
				}
			}
			
			Integer filteredCount = ((Long)criteria.setProjection(Projections.rowCount()).uniqueResult()).intValue();
			
			criteria.setProjection(null);
			criteria.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);
			
			criteria.setFirstResult(pageSize * (page - 1)).setMaxResults(pageSize);
			
			foundations.addAll(criteria.list());
			
			return filteredCount;
		}
		finally
		{
			if (session != null && session.isOpen())
			{
				session.close();
			}
		}
	}
	
	/**
	 * 매장 정보 개수를 가져온다.
	 * @return 매장 정보 개수가 반환된다.
	 */
	public Long countFoundation()
	{
		Session session = this._sessionFactory.openSession();
		
		try
		{
			return (Long)session.createCriteria(Foundation.class).setProjection(Projections.rowCount()).uniqueResult();
		}
		finally
		{
			if (session != null && session.isOpen())
			{
				session.close();
			}
		}
	}
	
	/**
	 * 주어진 매장 아이디에 대한 매장 정보를 열람한다. 
	 * @param foundationId 열람하려는 매장의 아이디를 설정한다.
	 * @return 주어진 매장 아이디에 대한 매장 정보가 반환된다. 존재하지 않는 경우 null이 반환된다.
	 */
	public Foundation getFoundation(Long foundationId)
	{
		Session session = this._sessionFactory.openSession();
		
		try
		{
			Criteria criteria = session.createCriteria(Foundation.class)
					.add(Restrictions.eq("foundationId", foundationId));
			
			Foundation foundation = (Foundation)criteria.uniqueResult();
			
			return foundation;
		}
		finally
		{
			if (session != null && session.isOpen())
			{
				session.close();
			}
		}
	}
	
	/**
	 * 매장 정보를 저장한다.
	 * @param foundationId 저장하려는 매장의 아이디를 설정한다. 새로운 매장일 경우, 0이 설정된다.
	 * @param foundation 저장하려는 매장 정보를 설정한다.
	 * @return 매장 정보가 저장되면 true, 그렇지 않으면 false가 반환된다. 
	 */
	public boolean saveFoundation(Long foundationId, Foundation foundation)
	{
		Session session = this._sessionFactory.openSession();
		
		Transaction transaction = session.beginTransaction();
		
		try
		{
			if (foundationId > 0)
			{
				foundation.setFoundationId(foundationId);
				
				session.update(foundation);
			}
			else
			{
				session.save(foundation);				
			}
			
			transaction.commit();
			
			return true;
		}
		catch (Exception error)
		{
			transaction.rollback();
			
			throw error;
		}
		finally
		{
			if (session != null && session.isOpen())
			{
				session.close();
			}
		}
	}
}